package com.dcdata.haixin.decoder;

import com.dcdata.haixin.bean.*;
import com.dcdata.haixin.enums.DataType;
import com.dcdata.haixin.enums.body.DaoLiItemIndex;
import com.dcdata.haixin.enums.body.GpsItemIndex;
import com.dcdata.haixin.enums.body.LoginItemIndex;
import com.dcdata.haixin.enums.body.ShakeHandItem;
import com.dcdata.haixin.enums.head.HeadItemIndex;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufUtil;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.ReplayingDecoder;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.extern.java.Log;

import java.util.ArrayList;
import java.util.List;

/**
 * @author: xinmi
 * @date: 2020/11/26
 * @description:
 */
@Log
public class MessageDecoder extends ReplayingDecoder {

    @Override
    protected void decode(ChannelHandlerContext channelHandlerContext, ByteBuf in, List<Object> out) throws Exception {
        Message msg = new Message();

        byte start = in.readByte();
        if (start != Message.START) {
            log.info("start =" + start);
            return;
        }
        short totalLength = in.readShort();

        if (totalLength <= 8) {
            log.info("totalLength not correct!");
            return;
        }
        msg.setTotalLengh(totalLength);

        byte version = in.readByte();
        if (version != Message.VERSION) {
            log.info("version = " + version);
            return;
        }

        byte packageType = in.readByte();
        msg.setPackageType(packageType);

        short headLength = in.readShort();
        if (headLength <= 3) {
            log.info("headLength not correct!");
            return;
        }
        short bodyLength = (short) (totalLength - headLength - 8);

        if (bodyLength <= 3) {
            log.info("bodyLength not correct!");
            return;
        }

        switch (packageType) {
            case 0x01:
                procLogin(msg, in, headLength, bodyLength);
                break;
            case 0x03:
                procGps(msg, in, headLength, bodyLength);
                break;
            case 0x04:
                procDaoLi(msg, in, headLength, bodyLength);
                break;
            case 0x08:
                procShakeHand(msg, in, headLength, bodyLength);
                break;
            default:
                log.info("packageType not correct!");
                return;
        }

        byte end = in.readByte();
        if (end != Message.END) {
            log.info("end =" + end);
            return;
        }

        out.add(msg);
    }

    void procLogin(Message msg, ByteBuf in, short headLength, short bodyLength) {
        msg.setHeadLengh(headLength);

        if (in.isReadable(headLength)) {
            ByteBuf headBuf = in.readBytes(headLength);
            Head head = getHead(headBuf);
            msg.setHead(head);
        }

        if (in.isReadable(bodyLength)) {
            ByteBuf bodyBuf = in.readBytes(bodyLength);
            Body body = getLoginBody(bodyBuf);
            msg.setBody(body);
        }
    }

    void procGps(Message msg, ByteBuf in, short headLength, short bodyLength) {
        msg.setHeadLengh(headLength);

        if (in.isReadable(headLength)) {
            ByteBuf headBuf = in.readBytes(headLength);
            Head head = getHead(headBuf);
            msg.setHead(head);
        }

        if (in.isReadable(bodyLength)) {
            ByteBuf bodyBuf = in.readBytes(bodyLength);
            Body body = getGpsBody(bodyBuf);
            msg.setBody(body);
        }
    }

    void procDaoLi(Message msg, ByteBuf in, short headLength, short bodyLength) {
        msg.setHeadLengh(headLength);

        if (in.isReadable(headLength)) {
            ByteBuf headBuf = in.readBytes(headLength);
            Head head = getHead(headBuf);
            msg.setHead(head);
        }

        if (in.isReadable(bodyLength)) {
            ByteBuf bodyBuf = in.readBytes(bodyLength);
            Body body = getDaoLiBody(bodyBuf);
            msg.setBody(body);
        }
    }

    void procShakeHand(Message msg, ByteBuf in, short headLength, short bodyLength) {
        msg.setHeadLengh(headLength);

        if (in.isReadable(headLength)) {
            ByteBuf headBuf = in.readBytes(headLength);
            Head head = getHead(headBuf);
            msg.setHead(head);
        }

        if (in.isReadable(bodyLength)) {
            ByteBuf bodyBuf = in.readBytes(bodyLength);
            Body body = getShakeHandBody(bodyBuf);
            msg.setBody(body);
        }
    }

    Body getLoginBody(ByteBuf bodyBuf) {
        Body body = new Body();

        List<LoginItem> loginItemList = new ArrayList<>();

        int bodyPos = 0;
        while (bodyPos < bodyBuf.capacity()) {
            LoginItem loginItem = new LoginItem();
            loginItem.setLoginItemIndex(getLoginItemIndex(bodyBuf.readByte()));

            DataType dataType = getDataType(bodyBuf.readByte());
            loginItem.setDataType(dataType);
            bodyPos += 2;

            ValuePos valuePos = getValue(dataType, bodyBuf);
            loginItem.setValue(valuePos.getValuse());
            bodyPos += valuePos.getPos();

            loginItemList.add(loginItem);
        }

        body.setLoginItemList(loginItemList);
        return body;
    }

    Body getGpsBody(ByteBuf bodyBuf) {
        Body body = new Body();

        List<GpsItem> gpsItemList = new ArrayList<>();

        int bodyPos = 0;
        while (bodyPos < bodyBuf.capacity()) {
            GpsItem gpsItem = new GpsItem();
            gpsItem.setGpsItemIndex(getGpsItemIndex(bodyBuf.readByte()));

            DataType dataType = getDataType(bodyBuf.readByte());
            gpsItem.setDataType(dataType);
            bodyPos += 2;

            ValuePos valuePos = getValue(dataType, bodyBuf);
            if (valuePos != null) {
                gpsItem.setValue(valuePos.getValuse());
                bodyPos += valuePos.getPos();
            }

            gpsItemList.add(gpsItem);
        }

        body.setGpsItemList(gpsItemList);
        return body;
    }

    GpsItemIndex getGpsItemIndex(byte index) {
        GpsItemIndex gpsItemIndex = null;
        switch (index) {
            case 1:
                gpsItemIndex = GpsItemIndex.LONGITUDE_HIGH;
                break;
            case 2:
                gpsItemIndex = GpsItemIndex.LONGITUDE_LOW;
                break;
            case 3:
                gpsItemIndex = GpsItemIndex.LATITUDE_HIGH;
                break;
            case 4:
                gpsItemIndex = GpsItemIndex.LATITUDE_LOW;
                break;
            case 5:
                gpsItemIndex = GpsItemIndex.HEIGHT;
                break;
            case 6:
                gpsItemIndex = GpsItemIndex.INSTANT_SPEED;
                break;
            case 7:
                gpsItemIndex = GpsItemIndex.DIRECTION_ANGLE;
                break;
            case 8:
                gpsItemIndex = GpsItemIndex.TIME;
                break;
            case 9:
                gpsItemIndex = GpsItemIndex.TIME_TYPE;
                break;
            case 0x0a:
                gpsItemIndex = GpsItemIndex.REPLACEMENT_SIGNS;
                break;
            case 0x0b:
                gpsItemIndex = GpsItemIndex.AVERAGE_VELOCITY;
                break;
            case 0x0c:
                gpsItemIndex = GpsItemIndex.TYPE_OF_TRAIN;
                break;
            case 0x0d:
                gpsItemIndex = GpsItemIndex.SUBLINE_NUMBER;
                break;
            case 0x0e:
                gpsItemIndex = GpsItemIndex.SITE_UNIFORM_NUMBER;
                break;
            case 0x0f:
                gpsItemIndex = GpsItemIndex.TYPE_OF_TRAIN_FLAG;
                break;
            case 0x10:
                gpsItemIndex = GpsItemIndex.VEHICLE_TURN_AROUND;
                break;
            case 0x11:
                gpsItemIndex = GpsItemIndex.CUMULATIVE_MILEAGE;
                break;
            case 0x12:
                gpsItemIndex = GpsItemIndex.CUMULATIVE_MILEAGE_SENSOR;
                break;
            case 0x13:
                gpsItemIndex = GpsItemIndex.RESIDUAL_OIL;
                break;
            case 0x14:
                gpsItemIndex = GpsItemIndex.AIR_CONDITION;
                break;
            case (byte) 0xA0:
                gpsItemIndex = GpsItemIndex.LOCATION_INFORMATION_SET;
                break;
            case (byte) 0xb0:
                gpsItemIndex = GpsItemIndex.EXTENSION_FIELD1;
                break;
            case (byte) 0xb1:
                gpsItemIndex = GpsItemIndex.EXTENSION_FIELD2;
                break;
            case (byte) 0xb2:
                gpsItemIndex = GpsItemIndex.EXTENSION_FIELD3;
                break;
            case (byte) 0xb3:
                gpsItemIndex = GpsItemIndex.EXTENSION_FIELD4;
                break;
            case (byte) 0xb4:
                gpsItemIndex = GpsItemIndex.EXTENSION_FIELD5;
                break;
            case (byte) 0xb5:
                gpsItemIndex = GpsItemIndex.EXTENSION_FIELD6;
                break;
            case (byte) 0xb6:
                gpsItemIndex = GpsItemIndex.EXTENSION_FIELD7;
                break;
            case (byte) 0xb7:
                gpsItemIndex = GpsItemIndex.EXTENSION_FIELD8;
                break;
            case (byte) 0xb8:
                gpsItemIndex = GpsItemIndex.EXTENSION_FIELD9;
                break;
            case (byte) 0xb9:
                gpsItemIndex = GpsItemIndex.EXTENSION_FIELD10;
                break;
            case (byte) 0xba:
                gpsItemIndex = GpsItemIndex.EXTENSION_FIELD11;
                break;
            case (byte) 0xbb:
                gpsItemIndex = GpsItemIndex.EXTENSION_FIELD12;
                break;
            case (byte) 0xbc:
                gpsItemIndex = GpsItemIndex.EXTENSION_FIELD13;
                break;
            case (byte) 0xbd:
                gpsItemIndex = GpsItemIndex.EXTENSION_FIELD14;
                break;
            case (byte) 0xbe:
                gpsItemIndex = GpsItemIndex.EXTENSION_FIELD15;
                break;
            case (byte) 0xbf:
                gpsItemIndex = GpsItemIndex.EXTENSION_FIELD16;
                break;
            case (byte) 0xc0:
                gpsItemIndex = GpsItemIndex.RESERVATION;
                break;
            case (byte) 0xc1:
                gpsItemIndex = GpsItemIndex.RESERVATION1;
                break;
            case (byte) 0xc2:
                gpsItemIndex = GpsItemIndex.RESERVATION2;
                break;
            case (byte) 0xc3:
                gpsItemIndex = GpsItemIndex.SITE_NUMBER;
                break;
            case (byte) 0xc4:
                gpsItemIndex = GpsItemIndex.VEHICLE_STATUS_POSITION;
                break;
            case (byte) 0xc5:
                gpsItemIndex = GpsItemIndex.GPS_STATUS;
                break;
            case (byte) 0xc6:
                gpsItemIndex = GpsItemIndex.AUXILIARY_DATA1;
                break;
            case (byte) 0xc7:
                gpsItemIndex = GpsItemIndex.AUXILIARY_DATA2;
                break;
            case (byte) 0xc8:
                gpsItemIndex = GpsItemIndex.AUXILIARY_DATA3;
                break;
            case (byte) 0xc9:
                gpsItemIndex = GpsItemIndex.AUXILIARY_DATA4;
                break;
            case (byte) 0xca:
                gpsItemIndex = GpsItemIndex.AUXILIARY_DATA5;
                break;
        }

        return gpsItemIndex;
    }

    Body getShakeHandBody(ByteBuf bodyBuf) {
        Body body = new Body();

        List<ShakeHandItem> shakeHandItemList = new ArrayList<>();

        int bodyPos = 0;
        while (bodyPos < bodyBuf.capacity()) {
            ShakeHandItem shakeHandItem = new ShakeHandItem();

            bodyBuf.skipBytes(1);
            bodyPos += 1;

            shakeHandItem.setDataType(getDataType(bodyBuf.readByte()));
            shakeHandItem.setValue(bodyBuf.readByte());
            bodyPos += 2;
            shakeHandItemList.add(shakeHandItem);
        }

        body.setShakeHandItemList(shakeHandItemList);
        return body;
    }

    Body getDaoLiBody(ByteBuf bodyBuf) {
        Body body = new Body();

        List<DaoLiItem> daoLiItemList = new ArrayList<>();

        int bodyPos = 0;
        while (bodyPos < bodyBuf.capacity()) {
            DaoLiItem daoLiItem = new DaoLiItem();
            daoLiItem.setDaoLiItemIndex(getDaoLeiItemIndex(bodyBuf.readByte()));
            DataType dataType = getDataType(bodyBuf.readByte());
            daoLiItem.setDataType(dataType);
            bodyPos += 2;

            ValuePos valuePos = getValue(dataType, bodyBuf);
            daoLiItem.setValue(valuePos.getValuse());
            bodyPos += valuePos.getPos();

            daoLiItemList.add(daoLiItem);
        }

        body.setDaoLiItemList(daoLiItemList);
        return body;
    }

    DaoLiItemIndex getDaoLeiItemIndex(byte index) {
        DaoLiItemIndex daoLiItemIndex = null;

        switch (index) {
            case 1:
                daoLiItemIndex = DaoLiItemIndex.ARRIVAL_SIGNS;
                break;
            case 2:
                daoLiItemIndex = DaoLiItemIndex.SITE_NUMBER;
                break;
            case 3:
                daoLiItemIndex = DaoLiItemIndex.SUB_ROUTE_NUMBER;
                break;
            case 4:
                daoLiItemIndex = DaoLiItemIndex.SITE_PROPERTIES;
                break;
            case 5:
                daoLiItemIndex = DaoLiItemIndex.AUTOMATIC_MANUAL_MARKING;
                break;
            case 6:
                daoLiItemIndex = DaoLiItemIndex.ARRIVAL_TIME;
                break;
            case 7:
                daoLiItemIndex = DaoLiItemIndex.TIME_TYPE;
                break;
            case 8:
                daoLiItemIndex = DaoLiItemIndex.LINE_TYPE;
                break;
            case 9:
                daoLiItemIndex = DaoLiItemIndex.LINE_SEGMENT_NUMBER;
                break;
            case 0x0a:
                daoLiItemIndex = DaoLiItemIndex.REPLACEMENT_SIGNS;
                break;
            case 0x0b:
                daoLiItemIndex = DaoLiItemIndex.SECOND_STATION_SIGN;
                break;
            case 0x0c:
                daoLiItemIndex = DaoLiItemIndex.SITE_NUMBER_FOR_NEXT_STOP;
                break;
            case 0x0d:
                daoLiItemIndex = DaoLiItemIndex.LONGITUDE_HIGH;
                break;
            case 0x0e:
                daoLiItemIndex = DaoLiItemIndex.LONGITUDE_LOW;
                break;
            case 0x0f:
                daoLiItemIndex = DaoLiItemIndex.LATITUDE_HIGH;
                break;
            case 0x10:
                daoLiItemIndex = DaoLiItemIndex.LATITUDE_LOW;
                break;
            case 0x11:
                daoLiItemIndex = DaoLiItemIndex.HEIGHT;
                break;
            case 0x12:
                daoLiItemIndex = DaoLiItemIndex.INSTANT_SPEED;
                break;
            case 0x13:
                daoLiItemIndex = DaoLiItemIndex.DIRECTION_ANGLE;
                break;
            case 0x14:
                daoLiItemIndex = DaoLiItemIndex.AVERAGE_VELOCITY;
                break;
            case 0x15:
                daoLiItemIndex = DaoLiItemIndex.SITE_UNIFORM_NUMBER;
                break;
            case 0x16:
                daoLiItemIndex = DaoLiItemIndex.TYPE_OF_TRAIN;
                break;
            case 0x17:
                daoLiItemIndex = DaoLiItemIndex.TYPE_OF_TRAIN_FLAG;
                break;
            case 0x18:
                daoLiItemIndex = DaoLiItemIndex.CUMULATIVE_MILEAGE;
                break;
            case 0x19:
                daoLiItemIndex = DaoLiItemIndex.VEHICLE_COMMUNICATION_NUMBER;
                break;
            case (byte) 0xa0:
                daoLiItemIndex = DaoLiItemIndex.LOCATION_INFORMATION_SET;
                break;
            case (byte) 0xb0:
                daoLiItemIndex = DaoLiItemIndex.EXTENSION_FIELD1;
                break;
            case (byte) 0xb1:
                daoLiItemIndex = DaoLiItemIndex.EXTENSION_FIELD2;
                break;
            case (byte) 0xb2:
                daoLiItemIndex = DaoLiItemIndex.EXTENSION_FIELD3;
                break;
            case (byte) 0xb3:
                daoLiItemIndex = DaoLiItemIndex.EXTENSION_FIELD4;
                break;
            case (byte) 0xb4:
                daoLiItemIndex = DaoLiItemIndex.EXTENSION_FIELD5;
                break;
            case (byte) 0xb5:
                daoLiItemIndex = DaoLiItemIndex.EXTENSION_FIELD6;
                break;
            case (byte) 0xb6:
                daoLiItemIndex = DaoLiItemIndex.EXTENSION_FIELD7;
                break;
            case (byte) 0xb7:
                daoLiItemIndex = DaoLiItemIndex.EXTENSION_FIELD8;
                break;
            case (byte) 0xb8:
                daoLiItemIndex = DaoLiItemIndex.EXTENSION_FIELD9;
                break;
            case (byte) 0xb9:
                daoLiItemIndex = DaoLiItemIndex.EXTENSION_FIELD10;
                break;
            case (byte) 0xba:
                daoLiItemIndex = DaoLiItemIndex.EXTENSION_FIELD11;
                break;
            case (byte) 0xbb:
                daoLiItemIndex = DaoLiItemIndex.EXTENSION_FIELD12;
                break;
            case (byte) 0xbc:
                daoLiItemIndex = DaoLiItemIndex.EXTENSION_FIELD13;
                break;
            case (byte) 0xbd:
                daoLiItemIndex = DaoLiItemIndex.EXTENSION_FIELD14;
                break;
            case (byte) 0xbe:
                daoLiItemIndex = DaoLiItemIndex.EXTENSION_FIELD15;
                break;
            case (byte) 0xbf:
                daoLiItemIndex = DaoLiItemIndex.EXTENSION_FIELD16;
                break;
            case (byte) 0xc0:
                daoLiItemIndex = DaoLiItemIndex.RESERVATION;
                break;
            case (byte) 0xc1:
                daoLiItemIndex = DaoLiItemIndex.RESERVATION1;
                break;
            case (byte) 0xc2:
                daoLiItemIndex = DaoLiItemIndex.RESERVATION2;
                break;
            case (byte) 0xc3:
                daoLiItemIndex = DaoLiItemIndex.CUMULATIVE_MILEAGE_SENSOR;
                break;
        }
        return daoLiItemIndex;
    }

    @Data
    @AllArgsConstructor
    private class ValuePos {
        byte[] valuse;
        int pos;
    }

    ValuePos getValue(DataType dateType, ByteBuf bodyBuf) {
        if (dateType == null) {
            return null;
        }
        switch (dateType) {
            case UINT32:
                byte[] valuse4 = new byte[4];
                valuse4[0] = (byte) bodyBuf.readByte();
                valuse4[1] = (byte) bodyBuf.readByte();
                valuse4[2] = (byte) bodyBuf.readByte();
                valuse4[3] = (byte) bodyBuf.readByte();
                return new ValuePos(valuse4, 4);
            case UNIT16:
                byte[] valuse2 = new byte[2];
                valuse2[0] = (byte) bodyBuf.readByte();
                valuse2[1] = (byte) bodyBuf.readByte();
                return new ValuePos(valuse2, 2);
            case BYTE:
                byte[] valuse = new byte[1];
                valuse[0] = (byte) bodyBuf.readByte();
                return new ValuePos(valuse, 1);
            case COMPOSITE:
                int clength = bodyBuf.readByte();
                byte[] cvaluse = new byte[clength];
                bodyBuf.readBytes(clength).duplicate().readBytes(cvaluse);
                return new ValuePos(cvaluse, clength + 1);
            case BIGBUF:
                byte blength = bodyBuf.readByte();
                byte[] bvaluse = new byte[blength];
                bodyBuf.readBytes(blength).duplicate().readBytes(bvaluse);
                return new ValuePos(bvaluse, blength + 1);
            case TIME:
                byte[] tvaluse = new byte[7];
                bodyBuf.readBytes(7).duplicate().readBytes(tvaluse);
                return new ValuePos(tvaluse, 7);
            case SEQUENCE:
                byte slength = bodyBuf.readByte();
                byte[] svaluse = new byte[slength];
                bodyBuf.readBytes(slength).duplicate().readBytes(svaluse);
                return new ValuePos(svaluse, slength + 1);
            case GPSSET:
                byte[] gvaluse = new byte[23];
                bodyBuf.readBytes(23).duplicate().readBytes(gvaluse);
                return new ValuePos(gvaluse, 23);
        }
        return null;
    }

    LoginItemIndex getLoginItemIndex(byte index) {
        LoginItemIndex loginItemIndex = null;
        switch (index) {
            case 1:
                loginItemIndex = LoginItemIndex.USER_TYPE;
                break;
            case 2:
                loginItemIndex = LoginItemIndex.USER_ID;
                break;
            case 4:
                loginItemIndex = LoginItemIndex.LOGIN_REASON;
                break;
            case 8:
                loginItemIndex = LoginItemIndex.SHAKEHAND_PERIOD;
                break;
        }
        return loginItemIndex;
    }

    Head getHead(ByteBuf headBuf) {
        Head head = new Head();
        List<HeadItem> headItemList = new ArrayList<>();
        int headPos = 0;
        while (headPos < headBuf.capacity()) {
            HeadItem headItem = new HeadItem();

            headItem.setHeadItemIndex(getHeadItemIndex(headBuf.readByte()));

            DataType dataType = getDataType(headBuf.readByte());
            headItem.setDataType(dataType);
            headPos += 2;

            ValuePos valuePos = getValue(dataType, headBuf);
            if (valuePos != null) {
                headItem.setValue(valuePos.getValuse());
                headPos += valuePos.getPos();
            }

            headItemList.add(headItem);
        }
        head.setHeadItemList(headItemList);
        return head;
    }

    HeadItemIndex getHeadItemIndex(byte index) {
        HeadItemIndex headItemIndex = null;
        switch (index) {
            case 1:
                headItemIndex = HeadItemIndex.SOURCE_ADDRESS_ORGNIZATION;
                break;
            case 2:
                headItemIndex = HeadItemIndex.SOURCE_TYPE;
                break;
            case 3:
                headItemIndex = HeadItemIndex.SOURCE_ADDRESS;
                break;
            case 4:
                headItemIndex = HeadItemIndex.DESTINATION_TYPE;
                break;
            case 5:
                headItemIndex = HeadItemIndex.DESTINATION_ADDRESS;
                break;
            case 6:
                headItemIndex = HeadItemIndex.RESPONE_FIELD;
                break;
            case 7:
                headItemIndex = HeadItemIndex.PRIORITY;
                break;
            case 8:
                headItemIndex = HeadItemIndex.SEQUENCE;
                break;
            case 9:
                headItemIndex = HeadItemIndex.TIMESTAMP;
                break;
            case 0x0a:
                headItemIndex = HeadItemIndex.END_FLAG;
                break;
        }
        return headItemIndex;
    }

    DataType getDataType(byte dt) {
        DataType dataType = null;
        switch (dt) {
            case 0x01:
                dataType = DataType.UINT32;
                break;
            case 0x02:
                dataType = DataType.UNIT16;
                break;
            case 0x03:
                dataType = DataType.BYTE;
                break;
            case 0x04:
                dataType = DataType.COMPOSITE;
                break;
            case 0x05:
                dataType = DataType.BIGBUF;
                break;
            case 0x06:
                dataType = DataType.TIME;
                break;
            case 0x07:
                dataType = DataType.SEQUENCE;
                break;
            case 0x08:
                dataType = DataType.GPSSET;
                break;
        }
        return dataType;
    }
}
